package cn.kwq.pcsystem.concurrent;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

import java.time.Duration;
import java.util.Collections;
import java.util.Timer;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * Created with IntelliJ IDEA.
 * 分布式锁
 * @Author: kwq
 * @Date: 2023/03/13/17:30
 * @Description:
 */
@Slf4j
public class RedisLock  {

    private final StringRedisTemplate redisTemplate;

    //持有锁超时时间
    private static final Long LOCK_TIME_OUT = 10L;
    //自旋获取锁间隔
    public static final Long LOCK_WAIT_TIME = 50L;
    //获取锁的超时时间
    public static final Long TRY_TIME_OUT= 1000L;
    //用于tryLock和unlock不能同时进行
    private static final Object lock = new Object();

    private String ThreadUUID;

    public RedisLock(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
        //每次新建对象的时候创建唯一的uuid
        this.ThreadUUID= IdUtil.fastUUID();
    }



    /**
     * 加锁
     * @param key 锁id
     * @param threadUUID 每个获取锁线程唯一的uuid
     * @param lockAliveTime 锁存活时间
     */
    public void lock(String key,String threadUUID,Long lockAliveTime){
        TimeInterval timer = DateUtil.timer();
        long interval=0;
        // 最大自旋时间内获取锁
        while(interval<=TRY_TIME_OUT){
            if(tryLock(key,threadUUID,lockAliveTime)){
                interval += timer.interval();//花费毫秒数
                break;//跳出自旋循环
            }else {
                try {
                    Thread.sleep(LOCK_WAIT_TIME);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        if (interval>TRY_TIME_OUT){
            throw new RuntimeException("获取锁失败");
        }
    }
    /**
     * 加锁
     * @param key 锁id
     * @param threadUUID 每个获取锁线程唯一的uuid
     */
    public void lock(String key,String threadUUID) {
        TimeInterval timer = DateUtil.timer();
        long interval=0;
        // 最大自旋时间内获取锁
        while(interval<=TRY_TIME_OUT){
            if(tryLock(key,threadUUID)){
                interval += timer.interval();//花费毫秒数
                break;//跳出自旋循环
            }else {
                try {
                    Thread.sleep(LOCK_WAIT_TIME);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        if (interval>TRY_TIME_OUT){
            throw new RuntimeException("获取锁失败");
        }
    }

    /**
     * 加锁
     * @param key 锁id
     */
    public void lock(String key) {
        Assert.isTrue(ThreadUUID!=null,"UUID Error Redis Lock");
        TimeInterval timer = DateUtil.timer();
        long interval=0;
        // 最大自旋时间内获取锁
        while(interval<=TRY_TIME_OUT){
            if(tryLock(key,ThreadUUID)){
                interval += timer.interval();//花费毫秒数
                break;//跳出自旋循环
            }else {
                try {
                    Thread.sleep(LOCK_WAIT_TIME);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        if (interval>TRY_TIME_OUT){
            throw new RuntimeException("获取锁失败");
        }
    }

    /**
     * 尝试获取锁
     * @param key 锁id
     * @param UUID 获取锁线程的唯一标识
     * @return
     */
    public boolean tryLock(String key,String UUID) {
        synchronized (lock){
            return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, UUID, Duration.ofSeconds(LOCK_TIME_OUT)));
        }
    }

    /**
     * 尝试获取锁
     * @param key 锁id
     * @param UUID 获取锁线程的唯一标识
     * @param time 锁存活时间
     * @return
     */
    public boolean tryLock(String key,String UUID,long time)  {
        synchronized (lock){
            return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, UUID, Duration.ofSeconds(time)));
        }
    }

    /**
     * 释放锁
     * @param key 锁id
     * @param value 获取锁线程的唯一标识
     * @return
     */
    public Long unlock(String key,String value) {
        synchronized (lock){
            //Lua脚本保证获取和删除的原子性
            String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript,Long.class);
            return (Long) this.redisTemplate.execute(redisScript, Collections.singletonList(key),value);
        }
    }

    public Long unlock(String key) {
        Assert.isTrue(ThreadUUID!=null,"UUID Error Redis Lock");
        synchronized (lock){
            //Lua脚本保证获取和删除的原子性
            String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript,Long.class);
            return (Long) this.redisTemplate.execute(redisScript, Collections.singletonList(key),ThreadUUID);
        }
    }


}
